package com.sky.interceptor;

import com.sky.constant.JwtClaimsConstant;
import com.sky.context.BaseContext;
import com.sky.properties.JwtProperties;
import com.sky.utils.JwtUtil;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * jwt令牌校验的拦截器
 */
@Component
@Slf4j
public class JwtTokenAdminInterceptor implements HandlerInterceptor {

    @Autowired
    private JwtProperties jwtProperties;

    /**
     * 校验jwt
     *
     * @param request
     * @param response
     * @param handler
     * @returnu8
     *
     * @throws Exception
     */
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        log.info("当前线程id：{}", Thread.currentThread().getId());

        //判断当前拦截到的是Controller的方法还是其他资源
        if (!(handler instanceof HandlerMethod)) {
            //当前拦截到的不是动态方法，直接放行
            return true;
        }

        //1、从请求头中获取令牌
        String token = request.getHeader(jwtProperties.getAdminTokenName());

        //2、校验令牌
        try {
            log.info("jwt校验:{}", token);
            Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);
            Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());
            log.info("当前员工id：", empId);

            BaseContext.setCurrentId(empId);
            //保存员工id到当前线程中

            //3、通过，放行
            return true;
        } catch (Exception ex) {
            //4、不通过，响应401状态码
            response.setStatus(401);
            return false;
        }
    }
}

/*
这段代码是一个基于 Spring MVC 的 JWT 令牌校验拦截器，用于在请求到达控制器方法之前验证客户端携带的 JWT 令牌的有效性，实现对后台接口的安全保护。下面从功能、核心逻辑、关键技术点三个方面详细解释：
一、类的基本信息与作用
java
运行
@Component  // 标识为Spring组件，让Spring容器管理
@Slf4j     // Lombok注解，生成日志对象log
public class JwtTokenAdminInterceptor implements HandlerInterceptor {
    // 实现HandlerInterceptor接口，成为Spring MVC的拦截器
}

核心作用：拦截管理员相关的 HTTP 请求，校验请求中携带的 JWT 令牌是否有效，只有通过校验的请求才能访问目标接口（如员工管理、订单处理等），未携带令牌或令牌无效的请求会被拒绝（返回 401 状态码）。
二、核心方法与执行流程
拦截器的核心逻辑在 preHandle 方法中（该方法在请求到达控制器方法之前执行），完整流程如下：
1. 区分拦截对象：只拦截控制器方法
java
运行
// 判断当前拦截到的是否是Controller中的方法（动态资源）
if (!(handler instanceof HandlerMethod)) {
    // 若不是控制器方法（如静态资源：CSS、JS、图片等），直接放行
    return true;
}

为什么要做这个判断？
Spring MVC 的拦截器会拦截所有请求（包括静态资源），但我们只需要对后台接口（控制器方法）进行令牌校验，静态资源应直接放行，避免无意义的校验消耗资源。
2. 从请求头中获取 JWT 令牌
java
运行
// 从请求头中获取令牌，令牌的键名由配置文件指定（如"token"）
String token = request.getHeader(jwtProperties.getAdminTokenName());

关键细节：

jwtProperties 是自定义的配置类（通过 @Autowired 注入），存储了 JWT 相关配置（如令牌在请求头中的键名、加密密钥等）。
前端发送请求时，需要将登录时获取的 JWT 令牌放在请求头中，格式通常为：Header: { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." }。
3. 校验令牌的有效性
java
运行
try {
    // 解析JWT令牌，验证其合法性（签名是否正确、是否过期等）
    Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);

    // 从令牌中获取员工ID（登录时已存入令牌）
    Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());
    log.info("当前员工id：", empId);

    // 将员工ID存入当前线程的上下文（供后续业务使用）
    BaseContext.setCurrentId(empId);

    // 令牌有效，放行请求
    return true;
} catch (Exception ex) {
    // 令牌无效（如签名错误、已过期、格式错误等），返回401状态码
    response.setStatus(401);
    return false;
}

这部分是核心校验逻辑，分三个关键步骤：

步骤 1：解析并验证令牌
JwtUtil.parseJWT 方法会使用配置的密钥（adminSecretKey）验证令牌的签名是否正确，同时检查令牌是否过期。如果令牌无效（如被篡改、已过期），会抛出异常（如 SignatureException、ExpiredJwtException）。
步骤 2：提取员工 ID 并存储
令牌验证通过后，从 Claims（JWT 载荷，存储自定义数据的部分）中获取登录员工的 ID（EMP_ID 是自定义的常量键名）。
然后通过 BaseContext.setCurrentId(empId) 将员工 ID 存入当前线程的上下文（ThreadLocal），后续的 Service、Mapper 层可以直接从线程上下文中获取该 ID（无需通过方法参数传递），方便实现 “操作人记录” 等功能。
步骤 3：根据校验结果决定是否放行
令牌有效：返回 true，请求继续传递到目标控制器方法。
令牌无效：捕获异常，设置响应状态码为 401 Unauthorized（未授权），返回 false，请求被拦截，不会到达控制器。
三、关键技术点与设计思路
HandlerInterceptor 拦截器机制
Spring MVC 的拦截器可以在请求处理的三个阶段介入：
preHandle：请求到达控制器前执行（此处用于令牌校验）。
postHandle：控制器方法执行后、视图渲染前执行。
afterCompletion：整个请求完成后执行（如资源清理）。
本拦截器只重写了 preHandle，因为令牌校验需要在请求处理前完成。
JWT 令牌的作用
JWT（JSON Web Token）是一种无状态的身份认证机制，登录成功后由服务器生成并返回给客户端，客户端后续请求携带该令牌证明身份。相比传统的 Session 认证，JWT 更适合分布式系统（无需服务器存储会话信息）。
ThreadLocal 存储当前用户 ID
BaseContext 类内部通常使用 ThreadLocal 存储数据，保证在多线程环境下，每个请求线程只能访问自己的员工 ID，避免线程安全问题。例如：
java
运行
public class BaseContext {
    private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();

    public static void setCurrentId(Long id) {
        threadLocal.set(id);
    }

    public static Long getCurrentId() {
        return threadLocal.get();
    }
}

配置类与常量的使用
JwtProperties：通过 @ConfigurationProperties 注解绑定配置文件（如 application.yml）中的 JWT 配置，方便动态调整（如令牌过期时间、密钥）。
JwtClaimsConstant：存储 JWT 载荷中的键名常量（如 EMP_ID），避免硬编码，提高代码可维护性。
四、总结
这个拦截器是后台接口安全的 “第一道防线”，通过以下流程保护接口：

拦截所有请求，只对控制器方法进行校验。
从请求头获取 JWT 令牌，验证其有效性。
令牌有效则提取员工 ID 并存储到线程上下文，放行请求；无效则返回 401 拒绝访问。
 */